﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Reflector.Graph.Graphs;
using Reflector.CodeModel;
using System.Windows.Forms;
using Reflector.CodeModel.Memory;

namespace MethodSeqGenerator
{
    class SeqGenStatementGraphVertexPopulatorVisitor : Visitor
    {
        HashSet<MethodInvocationHolder> relatedMIHSet = new HashSet<MethodInvocationHolder>();
        //This stores the set of nodes that are of interest including our expected method call

        List<String> searchStringList = null;
        StatementGraph graph = null;

        //This variable holds the current method invocation holder so as to assign the return type back into the holder
        MethodInvocationHolder currentMethodInvocationHolder = null;
        
        //Added for visualization
        public Microsoft.Msagl.Drawing.Graph graphDrawing = null;
        public void setGraphDrawing(Microsoft.Msagl.Drawing.Graph graphDrawing)
        {
            this.graphDrawing = graphDrawing;
        }
        //End of code added for visualization

        public SeqGenStatementGraphVertexPopulatorVisitor(StatementGraph graph, List<String> searchStringList) 
            : base()
        {
            this.graph = graph;
            this.searchStringList = searchStringList;
        }

        //Function for capturing the nodes of interests
        public override void VisitMethodInvokeExpression(IMethodInvokeExpression value)
        {
            //First visit the arguments of the method invocation
            this.VisitExpressionCollection(value.Arguments);

            StatementVertex associatedStmtVertex = graph.GetVertex(currentStatement);
            IMethodReferenceExpression methodReferenceExpression = value.Method as IMethodReferenceExpression;
            if (methodReferenceExpression != null)
            {
                MethodInvocationHolder mihObj = new MethodInvocationHolder(value);
                mihObj.nestingDepth = this.nestingDepth;
                if(associatedStmtVertex.associatedMIList == null)
                {
                    associatedStmtVertex.associatedMIList = new List<IStatement>();
                }
                associatedStmtVertex.associatedMIList.Add(mihObj);

                //Verifying whether this node is of interest
                IMethodReference methodReference = methodReferenceExpression.Method;
                String returnType = SeqGenUtil.GetFullTypeName(methodReference.ReturnType.Type);
                if (searchStringList.Contains(returnType))
                    AddToIntNodeStore(returnType, currentStatement);                   
                
                if (searchStringList.Contains(mihObj.referenceClsName.getTypeName()))
                    relatedMIHSet.Add(mihObj);
                
                //SeqGenerator.graphDrawing.AddNode(mihObj.ToString());
                if (this.graphDrawing != null)
                    this.graphDrawing.FindNode(currentStatement.ToString()).Attr.Color = Microsoft.Msagl.Drawing.Color.Red;

                currentMethodInvocationHolder = mihObj;
            }

            //Suresh: The base method is not invoked as we would like to
            //have a custom visit where the arguments are visited first and then the main method invocation
            this.VisitExpression(value.Method);
        }

        public override void VisitObjectCreateExpression(IObjectCreateExpression value)
        {
            this.VisitExpressionCollection(value.Arguments);
            StatementVertex associatedStmtVertex = graph.GetVertex(currentStatement);
            ITypeReference typeRef = value.Constructor.DeclaringType as ITypeReference;
            String typeNameNew = typeRef.Namespace.ToString() + "." + typeRef.Name.ToString();

            if (searchStringList.Contains(typeNameNew))
                AddToIntNodeStore(typeNameNew, currentStatement); 
            
            MethodInvocationHolder mihObj = new MethodInvocationHolder(value);
            mihObj.nestingDepth = this.nestingDepth;
            if (associatedStmtVertex.associatedMIList == null)
            {
                associatedStmtVertex.associatedMIList = new List<IStatement>();
            }
            associatedStmtVertex.associatedMIList.Add(mihObj);
            if(this.graphDrawing != null)
                this.graphDrawing.FindNode(currentStatement.ToString()).Attr.Color = Microsoft.Msagl.Drawing.Color.Red;
            
            //Suresh: The base method is not invoked as we would like to
            //have a custom visit where the arguments are visited first and then the main
            //method invocation
            this.VisitType(value.Type);
            if (value.Constructor != null)
            {
                this.VisitMethodReference(value.Constructor);
            }
            if (value.Initializer != null)
            {
                this.VisitBlockExpression(value.Initializer);
            }
            //base.VisitObjectCreateExpression(value);
            currentMethodInvocationHolder = mihObj;
        }

        public HashSet<MethodInvocationHolder> getRelatedMIHSet()
        {
            return this.relatedMIHSet;
        }

        public override void VisitFieldReferenceExpression(IFieldReferenceExpression value)
        {
            IFieldReference ifrObj = value.Field;
            IExpression refObj = value.Target;  
            base.VisitFieldReferenceExpression(value);
        }

        public override void VisitPropertyReferenceExpression(IPropertyReferenceExpression value)
        {            
            MethodInvocationHolder mihObj = new MethodInvocationHolder(value);
            mihObj.nestingDepth = this.nestingDepth;
            StatementVertex associatedStmtVertex = graph.GetVertex(currentStatement);
            if (associatedStmtVertex.associatedMIList == null)
            {
                associatedStmtVertex.associatedMIList = new List<IStatement>();
            }
            associatedStmtVertex.associatedMIList.Add(mihObj);
            base.VisitPropertyReferenceExpression(value);
            currentMethodInvocationHolder = mihObj;
        }

        private IStatement currentStatement = null;
        public override void VisitStatement(IStatement statement)
        {
            int hashCode = statement.GetHashCode();

            currentMethodInvocationHolder = null;
            currentStatement = statement;
            IBlockStatement blockStatement = statement as IBlockStatement;
            if (blockStatement != null)
            {
                if (blockStatement.Statements.Count > 0)
                {
                    this.graph.AddVertex(statement);
                    if(this.graphDrawing != null)
                        this.graphDrawing.AddNode(statement.ToString());
                }
                                               
                base.VisitStatement(statement);
            }
            else
            {
                //add vertex in the graph
                if (statement != null)
                {
                    try
                    {
                        //A Bugfix to prevent NullReferenceException where throw statement
                        //can be used without any associated expression statement
                        IThrowExceptionStatement ithObj = statement as IThrowExceptionStatement;
                        if (ithObj != null && ithObj.Expression == null)
                        {
                            ithObj.Expression = new MyObjectCreateExpression();
                        }

                        this.graph.AddVertex(statement);
                    }                    
                    catch (System.NullReferenceException ex)
                    {
                        //SeqGenerator.swLogging.WriteLine("DLLName:" + SeqGenerator.currentDLL + ", Class:"
                        //    + SeqGenerator.currentTypeDeclarationName + ", Methodname:"
                        //    + SeqGenerator.currentMethodName + "Message:" + ex.Message);
                    }
                    catch (System.ArgumentException ex)
                    {
                        //MessageBox.Show(statement.ToString());
                        //SeqGenerator.swLogging.WriteLine("DLLName:" + SeqGenerator.currentDLL + ", Class:"
                        //    + SeqGenerator.currentTypeDeclarationName + ", Methodname:"
                        //    + SeqGenerator.currentMethodName + "Message:" + ex.Message);
                    }

                    if(this.graphDrawing != null)
                        this.graphDrawing.AddNode(statement.ToString());
                }
                base.VisitStatement(statement);
            }
        }

        public override void VisitAssignExpression(IAssignExpression value)
        {
            TypeHolder tpObj = SeqGenUtil.ExtractTypeHolder(value.Target);
            this.VisitExpression(value.Target);

            this.VisitExpression(value.Expression);
            if((value.Expression is IMethodInvokeExpression || value.Expression is IObjectCreateExpression ||
               value.Expression is IPropertyReferenceExpression) && currentMethodInvocationHolder != null)
            {
                if (!SeqGenUtil.IsPrimitiveType(tpObj.getTypeName()) && !tpObj.getTypeName().Contains("UNKNOWN"))
                {
                    currentMethodInvocationHolder.returnType.setTypeName(tpObj.getTypeName());
                    currentMethodInvocationHolder.returnType.setVarName(tpObj.getVarName());

                    //We can set the reference variable name also if the expression type
                    //is an ObjectCreateExpression
                    if (value.Expression is IObjectCreateExpression)
                        currentMethodInvocationHolder.referenceClsName.setVarName(tpObj.getVarName());
                }
            }

            //A case where a method invocation is created for a property reference. Store the property value if it a primitive type
            if (value.Target is IPropertyReferenceExpression && currentMethodInvocationHolder != null
                && currentMethodInvocationHolder.bPropertyReference)
            {
                TypeHolder typObj = SeqGenUtil.ExtractTypeHolder(value.Expression);
                if (SeqGenerator.primitiveDataTypes.Contains(typObj.getTypeName()))
                    currentMethodInvocationHolder.propertyReferenceValue = typObj.getVarName();
            }

            if (currentMethodInvocationHolder != null && searchStringList.Contains(currentMethodInvocationHolder.returnType.getTypeName()))
                AddToIntNodeStore(currentMethodInvocationHolder.returnType.getTypeName(), currentStatement);
        }

        public override void VisitCastExpression(ICastExpression value)
        {
            this.VisitType(value.TargetType);
            this.VisitExpression(value.Expression);
            if (currentMethodInvocationHolder != null)
            {
                currentMethodInvocationHolder.returnType.setTypeName(SeqGenUtil.GetFullTypeName(value.TargetType));
                if (searchStringList.Contains(currentMethodInvocationHolder.returnType.getTypeName()))
                    AddToIntNodeStore(currentMethodInvocationHolder.returnType.getTypeName(), currentStatement);                    
            }
        }


        /**
         * Function to capture interesting nodes for each search string
         **/
        private void AddToIntNodeStore(String searchString, IStatement intNode)
        {
            HashSet<IStatement> intNodeList = (HashSet<IStatement>)SeqGenerator.searchStringToNodes[searchString];
            if (intNodeList == null)
            {
                intNodeList = new HashSet<IStatement>();
                SeqGenerator.searchStringToNodes.Add(searchString, intNodeList);
            }

            intNodeList.Add(intNode);
        }
    }
}
